package com.syt.mina.handler;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.mina.core.session.IoSession;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.support.HandlerMethodResolver;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;

import com.syt.mina.handler.service.HandlerAdapter;
import com.syt.mina.handler.service.HandlerMapping;
import com.syt.util.UrlPathHelper;

public class AMFHandlerAdapter implements HandlerAdapter {

	private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
		new HashMap<Class<?>, ServletHandlerMethodResolver>();
	private UrlPathHelper urlPathHelper = new AMFPathHelper();
	private PathMatcher pathMatcher = new AntPathMatcher();
	
	@Override
	public void handle(IoSession session, Object message, Object handler) throws Exception{
		ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
		Method handlerMethod = methodResolver.resolveHandlerMethod(session, message);
		Object[] args = {session,message};
		handlerMethod.invoke(handler, args);
	}
	
	
	
	/**
	 * Build a HandlerMethodResolver for the given handler type.
	 */
	private ServletHandlerMethodResolver getMethodResolver(Object handler) {
		Class handlerClass = ClassUtils.getUserClass(handler);
		synchronized (this.methodResolverCache) {
			ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
			if (resolver == null) {
				resolver = new ServletHandlerMethodResolver(handlerClass);
				this.methodResolverCache.put(handlerClass, resolver);
			}
			return resolver;
		}
	}
	
	@Override
	public boolean supports(Object handler) {
		// TODO Auto-generated method stub
		return true;
	}
	
	private class ServletHandlerMethodResolver extends HandlerMethodResolver {
		private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>();

		private ServletHandlerMethodResolver(Class<?> handlerType) {
			init(handlerType);
		}
		
		@Override
		protected boolean isHandlerMethod(Method method) {
			if (this.mappings.containsKey(method)) {
				return true;
			}
			RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
			if (mapping != null) {
				String[] patterns = mapping.value();
				RequestMethod[] methods = new RequestMethod[0];
				String[] params = new String[0];
				String[] headers = new String[0];
				if (!hasTypeLevelMapping() || !Arrays.equals(mapping.method(), getTypeLevelMapping().method())) {
					methods = mapping.method();
				}
				if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) {
					params = mapping.params();
				}
				if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) {
					headers = mapping.headers();
				}
				RequestMappingInfo mappingInfo = new RequestMappingInfo(patterns, methods, params, headers);
				this.mappings.put(method, mappingInfo);
				return true;
			}
			return false;
		}
		
		
		private String getCombinedPattern(String methodLevelPattern, String lookupPath, IoSession session, Object message) {
			if (hasTypeLevelMapping() && (!ObjectUtils.isEmpty(getTypeLevelMapping().value()))) {
				String[] typeLevelPatterns = getTypeLevelMapping().value();
				for (String typeLevelPattern : typeLevelPatterns) {
					if (!typeLevelPattern.startsWith("/")) {
						typeLevelPattern = "/" + typeLevelPattern;
					}
					String combinedPattern = pathMatcher.combine(typeLevelPattern, methodLevelPattern);
					if (isPathMatchInternal(combinedPattern, lookupPath)) {
						return combinedPattern;
					}
				}
				return null;
			}
			String bestMatchingPattern = (String) session.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
			if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) {
				String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern);
				if (!combinedPattern.equals(bestMatchingPattern) &&
						(isPathMatchInternal(combinedPattern, lookupPath))) {
					return combinedPattern;
				}
			}
			if (isPathMatchInternal(methodLevelPattern, lookupPath)) {
				return methodLevelPattern;
			}
			return null;
		}
		
		private boolean isPathMatchInternal(String pattern, String lookupPath) {
			if (pattern.equals(lookupPath) || pathMatcher.match(pattern, lookupPath)) {
				return true;
			}
			boolean hasSuffix = pattern.indexOf('.') != -1;
			if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) {
				return true;
			}
			boolean endsWithSlash = pattern.endsWith("/");
			if (!endsWithSlash && pathMatcher.match(pattern + "/", lookupPath)) {
				return true;
			}
			return false;
		}

		
		public Method resolveHandlerMethod(IoSession session, Object message) throws Exception {
			String lookupPath = urlPathHelper.getLookupPathForRequest(session,message);
			for (Method handlerMethod : getHandlerMethods()) {
				RequestMappingInfo mappingInfo = this.mappings.get(handlerMethod);
				boolean match = false;
				if (mappingInfo.hasPatterns()) {
					for (String pattern : mappingInfo.getPatterns()) {
						if (!hasTypeLevelMapping() && !pattern.startsWith("/")) {
							pattern = "/" + pattern;
						}
						String combinedPattern = getCombinedPattern(pattern, lookupPath, session, message);
						if (combinedPattern != null) {
								match = true;
						}
					}
				}
				if (match) {
					return handlerMethod;
				}
			}
			throw new Exception();
		}
		
	}
	
	/**
	 * Holder for request mapping metadata.
	 */
	static class RequestMappingInfo {

		private final String[] patterns;

		private final RequestMethod[] methods;

		private final String[] params;

		private final String[] headers;

		RequestMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) {
			this.patterns = patterns != null ? patterns : new String[0];
			this.methods = methods != null ? methods : new RequestMethod[0];
			this.params = params != null ? params : new String[0];
			this.headers = headers != null ? headers : new String[0];
		}

		public boolean hasPatterns() {
			return patterns.length > 0;
		}

		public String[] getPatterns() {
			return patterns;
		}

		public int getMethodCount() {
			return methods.length;
		}

		public int getParamCount() {
			return params.length;
		}

		public int getHeaderCount() {
			return headers.length;
		}

		

		public Set<String> methodNames() {
			Set<String> methodNames = new LinkedHashSet<String>(methods.length);
			for (RequestMethod method : methods) {
				methodNames.add(method.name());
			}
			return methodNames;
		}

		@Override
		public boolean equals(Object obj) {
			RequestMappingInfo other = (RequestMappingInfo) obj;
			return (Arrays.equals(this.patterns, other.patterns) && Arrays.equals(this.methods, other.methods) &&
					Arrays.equals(this.params, other.params) && Arrays.equals(this.headers, other.headers));
		}

		@Override
		public int hashCode() {
			return (Arrays.hashCode(this.patterns) * 23 + Arrays.hashCode(this.methods) * 29 +
					Arrays.hashCode(this.params) * 31 + Arrays.hashCode(this.headers));
		}

		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			builder.append(Arrays.asList(patterns));
			if (methods.length > 0) {
				builder.append(',');
				builder.append(Arrays.asList(methods));
			}
			if (headers.length > 0) {
				builder.append(',');
				builder.append(Arrays.asList(headers));
			}
			if (params.length > 0) {
				builder.append(',');
				builder.append(Arrays.asList(params));
			}
			return builder.toString();
		}
	}


}
